using System;
using System.Collections.Generic;
using System.Collections;
using System.Data;

namespace ODX.Core
{
    /// <summary>
    /// The base interface which is implemented in each <see cref="Entity"/> collection class.
    /// </summary>
    public interface IEntityListBase
    {
        
    }


    internal interface IEntityList : IEntityListBase, ICollection
    {
        void Add(Entity e);
        void Add(params Entity[] es);
        bool Remove(Entity e);
        bool Contains(Entity e);
    }

    /// <summary>
    /// Generic interface used to create collection properties in <see cref="Entity"/> inheriters.
    /// </summary>
    /// <typeparam name="T"><see cref="Entity"/> type to be kept into the list.</typeparam>
    public interface IEntityList<T> : IEntityListBase, ICollection<T> where T : Entity
    {
        /// <summary>
        /// Adds entities to the list
        /// </summary>
        /// <param name="es">Entities to be added</param>
        void Add(params T[] es);
    }


    /// <summary>
    /// This class is used internally and returned from 1:N relations as list of children.
    /// </summary>
    /// <typeparam name="T">Child type.</typeparam>
    public class EntityChildList<T> : IEntityList, IEntityList<T> where T : Entity
    {
        private Entity parent;
        private DataRelation dataRelation;

        public EntityChildList(Entity parent, string dataRelation) : this(parent, parent.Session.Schema.Relations[dataRelation]) {}
        public EntityChildList(Entity parent, DataRelation dataRelation)
        {
            this.parent = parent;
            this.dataRelation = dataRelation;
            parent.Session.PreloadRelation(parent.ID, dataRelation);
        }

        public void Add(T e)
        {
            ((IEntityList)this).Add(e);
        }

        public bool Remove(T e)
        {
            return ((IEntityList)this).Remove(e);
        }

        public bool Contains(T e)
        {
            return ((IEntityList)this).Contains(e);
        }

        public int Count
        {
            get { return parent.GetRow(dataRelation.ParentTable.TableName).GetChildRows(dataRelation).Length; }
        }

        public IEnumerator<T> GetEnumerator()
        {
            DataRow parentRow = parent.GetRow(dataRelation.ParentTable.TableName);
            return new EntityChildListEnumerator<T>(parentRow.GetChildRows(dataRelation));
        }

        public void Clear()
        {
            foreach (T e in new List<T>(this))
                Remove(e);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            int i = arrayIndex;
            foreach (T e in this)
                array[i++] = e;
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        void ICollection.CopyTo(Array array, int index)
        {
            int i = index;
            foreach (T obj in this)
                array.SetValue(obj, i++);
        }

        public bool IsSynchronized
        {
            get { return true; }
        }

        public object SyncRoot
        {
            get { return GetType(); }
        }

        void IEntityList.Add(Entity e)
        {
            DataRow parentRow = parent.GetRow(dataRelation.ParentTable.TableName);
            DataRow childRow = e.GetRow(dataRelation.ChildTable.TableName);
            childRow.SetParentRow(parentRow, dataRelation);
        }

        public void Add(params Entity[] es)
        {
            foreach ( Entity e in es )
                ((IEntityList)this).Add(e);
        }

        bool IEntityList.Remove(Entity e)
        {
            DataRow childRow = e.GetRow(dataRelation.ChildTable.TableName);
            childRow.SetParentRow(null, dataRelation);
            return true;
        }

        bool IEntityList.Contains(Entity e)
        {
            DataRow childRow = e.GetRow(dataRelation.ChildTable.TableName);
            return new List<DataRow>(parent.GetRow(dataRelation.ParentTable.TableName).GetChildRows(dataRelation)).Contains(childRow);
        }

        public void Add(params T[] es)
        {
            foreach (T e in es)
                ((IEntityList)this).Add(e);
        }
    }


    internal class EntityChildListEnumerator<T> : IEnumerator<T> where T : Entity
    {
        private DataRow[] rows;
        int current = -1;

        public EntityChildListEnumerator(DataRow[] rows) { this.rows = rows; }
        public T Current { get { return (T)Entity.CreateWrapper(rows[current]); } }
        public bool MoveNext() { return ++current < rows.Length; }
        public void Reset() { current = -1; }
        public void Dispose() { rows = null; }
        object IEnumerator.Current { get { return Current; } }
    }

    /// <summary>
    /// This class is internally used and returned as N:M-linked entities.
    /// </summary>
    /// <typeparam name="T">Type of linked entities.</typeparam>
    public class EntityAssocList<T> : IEntityList, IEntityList<T> where T : Entity
    {
        private DataRelation p2l;
        private DataRelation l2ch;
        private Entity parent;

        private string parentColumn;
        private string childColumn;

        public EntityAssocList(Entity parent, string p2l, string l2ch)
            : this(parent,
            parent.Session.Schema.Relations[p2l],
            parent.Session.Schema.Relations[l2ch])
        {}

        public EntityAssocList(Entity parent, DataRelation p2l, DataRelation l2ch)
        {
            if ( l2ch.ChildTable != p2l.ChildTable )
                throw new OdxException("Inconsistent data relations!!!");
            if (p2l.DataSet != parent.Session.Schema)
                throw new OdxException("Relations don't belong to the parent session");

            this.parent = parent;
            this.p2l = p2l;
            this.l2ch = l2ch;
            
            parentColumn = p2l.ChildColumns[0].ColumnName;
            childColumn = l2ch.ChildColumns[0].ColumnName;

            parent.Session.PreloadAssociaton(parent.ID, p2l, l2ch);
        }

        public void Add(T e)
        {
            ((IEntityList)this).Add(e);
        }

        private DataRow FindLink(Entity e)
        {
            DataRow[] drs = p2l.ChildTable.Select(
                parentColumn + " = '" + parent.ID + "' and " +
                childColumn + " = '" + e.ID + "'");
            if (drs.Length == 0) return null;
            return drs[0];
        }
        public bool Remove(T e)
        {
            return ((IEntityList)this).Remove(e);
        }

        public int Count
        {
            get { return parent.GetRow(p2l.ParentTable.TableName).GetChildRows(p2l).Length; }
        }

        public bool Contains(T e)
        {
            return FindLink(e) != null;
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new EntityAssocEnumerator<T>(parent.GetRow(p2l.ParentTable.TableName).GetChildRows(p2l), l2ch);
        }

        public void Clear()
        {
            foreach ( T e in new List<T>(this))
                Remove(e);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            int i = arrayIndex;
            foreach (T e in this)
                array[i++] = e;
        }

        public bool IsReadOnly
        {
            get { return false; }
        }


        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        void IEntityList.Add(Entity e)
        {
            if ( FindLink(e) != null )
                return;

            DataRow link = p2l.ChildTable.NewRow();
            link[parentColumn] = parent.ID;
            link[childColumn] = e.ID;
            link["ID"] = Session.CreateID();
            link.Table.Rows.Add(link);
        }

        public void Add(params Entity[] es)
        {
            foreach (Entity e in es)
                ((IEntityList)this).Add(e);
        }

        bool IEntityList.Remove(Entity e)
        {
            DataRow link = FindLink(e);
            if (link != null)
            {
                link.Delete();
                return true;
            }
            return false;
        }

        bool IEntityList.Contains(Entity e)
        {
            return FindLink(e) != null;
        }

        void ICollection.CopyTo(Array array, int index)
        {
            int i = index;
            foreach (T obj in this)
                array.SetValue(obj, i++);
        }

        bool ICollection.IsSynchronized
        {
            get { return true; }
        }

        object ICollection.SyncRoot
        {
            get { return GetType(); }
        }

        public void Add(params T[] es)
        {
            foreach (T e in es)
                ((IEntityList)this).Add(e);
        }
    }

    internal class EntityAssocEnumerator<T> : IEnumerator<T> where T : Entity
    {
        private DataRelation l2ch;
        private DataRow[] links;
        private int current;
        public EntityAssocEnumerator(DataRow[] links, DataRelation l2ch)
        {
            this.links = links;
            this.l2ch = l2ch;
            current = -1;
        }

        public T Current { get { return (T)Entity.CreateWrapper(links[current].GetParentRow(l2ch)); } }
        public bool MoveNext() { return ++current < links.Length; }
        public void Reset() { current = -1; }
        public void Dispose() { links = null; }
        object IEnumerator.Current { get { return Current; } }
    }
}
